#include <stdlib.h>
#include <string.h>
#include <rpm.h>
#include <rpmdb.h>
#include <memory.h>
#include <arpa/inet.h>
#include "main.h"

void
ExtractAllScripts( RpmFileHandle theHandle, FILE* out )
{
    char * data;
    char * prog;

    data = rpm_getTagValueAsString( theHandle, RPMTAG_PREIN );

    if( data )
    {
        prog = rpm_getTagValueAsString( theHandle, RPMTAG_PREINPROG );
        fprintf( out, "preinstall scriptlet (using %s):\n%s\n", prog, data );
        free( prog );
        free( data );
    }
    data = rpm_getTagValueAsString( theHandle, RPMTAG_POSTIN );

    if( data )
    {
        prog = rpm_getTagValueAsString( theHandle, RPMTAG_POSTINPROG );
        fprintf( out, "postinstall scriptlet (using %s):\n%s\n", prog, data );
        free( prog );
        free( data );
    }

    data = rpm_getTagValueAsString( theHandle, RPMTAG_PREUN );
    if( data )
    {
        prog = rpm_getTagValueAsString( theHandle, RPMTAG_PREUNPROG );
        fprintf( out, "preuninstall scriptlet (using %s):\n%s\n", prog, data );
        free( prog );
        free( data );

    }
    data = rpm_getTagValueAsString( theHandle, RPMTAG_POSTUN );
    if( data )
    {
        prog = rpm_getTagValueAsString( theHandle, RPMTAG_POSTUNPROG );
        fprintf( out, "postuninstall scriptlet (using %s):\n%s\n", prog, data );
        free( prog );
        free( data );
    }
}

void
updatePackage_callback( const char * name, const char * asInstalled, int fd, void * userData )
{
#ifdef WORK_IN_PROGRESS
    RpmPackageElement toFind = { .name = name };

    const RpmPackage * pkg = (const RpmPackage*)( userData );

    RpmPackageElement * found = sglib_RpmPackageElement_find_member( &( pkg->contents ), &toFind );

    if( found )
    {
        free( found->asInstalled );
        found->asInstalled = strdup( asInstalled );

        /* Change permissions and such. */

    }

#endif

}

int
PerformUpgrade( const char *      root,
                RpmDataBaseHandle handle,
                int               verbose,
                char const**      filenames,
                int               numfilenames,
                FILE *            errorDest )
{
    int rc = 0;
    int file_index = 0;
    RpmStatus status;
    char * pkg_name=NULL;
    char * pkg_version=NULL;
    char ** pkg_filenames = NULL;
    int pkg_num_filenames = 0;

    RpmFileHandle * handles = (RpmFileHandle*)( calloc( numfilenames, sizeof( RpmFileHandle ) ) );
    memset( handles, 0, sizeof( RpmFileHandle )*numfilenames );

    while( file_index < numfilenames )
    {

        handles[file_index] = OpenRpmFile( filenames[file_index], &status );
        if( status!=rpmStsNoError )
        {
            fprintf( errorDest, "Cannot open %s\n", filenames[file_index] );
            rc = 1;
            goto bye;
        }
        file_index++;
    }

    file_index=0;
    while( file_index < numfilenames )
    {
        /* See if package is installed. */
        /* Get the package name.*/
        //RpmPackage * thePackage = rpm_GetPackageStructFromFile(handles[file_index] );

        //db_addPackage(handle,thePackage);

        /* Call preun on the to-be uninstalled package. */
        /* Call pre on the to-be installed package. */

        /* Generate delta list between files on the disk and files in the new package. */

        /* Extract new  files, handling config files as we go and recording the data into the XML tree. */
        ExtractArchiveContents( handles[file_index], root, updatePackage_callback, NULL );

        /* Delete extra files. */

        /* Call postun on the uninstalled package. */

        /* Call post on the now-installed package. */

        /* Clean-up */
        free( pkg_name ); pkg_name=NULL;
        free( pkg_version ); pkg_version=NULL;
        FreeStringArray( pkg_filenames, pkg_num_filenames );
        pkg_filenames=NULL;
        file_index++;
    }
    bye:
    free( pkg_name );
    free( pkg_version );
    file_index=0;
    while( file_index < numfilenames )
    {
        CloseRpmFile( handles[file_index] );
        file_index++;
    }
    return( rc );
}

int
main( int argc,  char ** argv )
{

    char const** filenames;
    int numfilenames;
    int verbose;
    command toDo;
    char  * rootDir;
    char * queryFormat=NULL;
    RpmDataBaseHandle dbHandle = NULL;

    int rc = parseCommandLine( argc,
                               argv,
                               &toDo,
                               &verbose,
                               &filenames,
                               &numfilenames,
                               &queryFormat,
                               &rootDir,
                               stdout,
                               stderr );

    if( rc != 0 )
    {
        return( rc );
    }

    if( toDo == cmd_help )
    {
        /* The parse command function printed the help for us. */
        return( 0 );
    }

    if( toDo == cmd_upgrade )
    {
        /* To upgrade, we need to open the database at 'rootDir' */
        RpmStatus status = rpmStsNoError;
        //dbHandle=db_open(rootDir,1,&status);
        if( status == rpmStsWriteError )
        {
            fprintf( stderr, "Could not write to %s.\n", rootDir );
            return( 1 );
        }
        if( status == rpmStsDatabaseLockError )
        {
            fprintf( stderr, "Could not lock the database.  Please try again.\n" );
            return( 1 );
        }
        int retVal = PerformUpgrade( rootDir, dbHandle, verbose, filenames, numfilenames, stderr );
        //db_close(dbHandle);
        return( retVal );

    }
    else if( toDo == cmd_verify )
    {
#if 0
        /* To verify, we need to open the database at 'root' */
        RpmStatus status;
        dbHandle=db_open( rootDir, 0, &status );
        int thisPackage = 0;

        if( numfilenames == 0 )
        {
            fprintf( stderr, "Specify a package to verify.\n" );
            return( 1 );
        }

        int allfound = 1;
        /* Check for all packages before starting. */
        while( thisPackage < numfilenames )
        {

            const RpmPackage * ourPackage = db_findRpmPackage( dbHandle, filenames[thisPackage] );
            if( ourPackage == NULL )
            {
                fprintf( stderr, "Package %s is not installed.\n", filenames[thisPackage] );
                allfound = 0;
            }
            thisPackage++;
        }

        if( !allfound )
        {
            return( 1 );
        }
        thisPackage = 0;
        int returnCode = 0;
        while( thisPackage < numfilenames )
        {

            const RpmPackage * ourPackage = db_findRpmPackage( dbHandle, filenames[thisPackage] );
            int rc = 0;     //VerifyPackage( dbHandle, ourPackage,stderr );
            if( rc != 0 )
            {
                returnCode = 1;
            }
            thisPackage++;
        }
#endif
        return( 0 );
    }
    else if( toDo == cmd_verify_all )
    {
        /* To upgrade, we need to open the database at 'root' */
        RpmStatus status;
        dbHandle=db_open( rootDir, 0, &status );
        return( PerformUpgrade( rootDir, dbHandle, verbose, filenames, numfilenames, stderr ) );
    }
    else if( toDo == cmd_query_tags && numfilenames == 0 )
    {
        for( size_t i = 1; i < sizeof( tagInfo )/sizeof( tagInfo[0] ); ++i )
        {
            printf( "%s\n", tagInfo[i].name );
        }
        return( 0 );
    }

    /* Easy commands. */

    if( numfilenames == 0 && toDo != cmd_query_all )
    {
        fprintf( stderr, "You must specify at least one package name or RPM file.\n" );
        return( 1 );
    }

    if( toDo == cmd_query_all )
    {

        return( 0 );
    }

    /* Commands available for only one package at a time. */

    if( numfilenames > 1 )
    {
        fprintf( stderr, "You may only query one package at a time.\n" );
        return( 1 );
    }

    RpmStatus status;
    RpmFileHandle theHandle = OpenRpmFile( filenames[0], &status );

    switch( toDo )
    {

        case cmd_query_list:
            //ListArchiveContents(theHandle,stdout);
            break;
        case cmd_query_pre_script:
            ExtractScriptContents( theHandle, rpmScriptPreinstall, 1, stdout );
            break;
        case cmd_query_post_script:
            ExtractScriptContents( theHandle, rpmScriptPostinstall, 1, stdout );
            break;
        case cmd_query_postun_script:
            ExtractScriptContents( theHandle, rpmScriptPreuninstall, 1, stdout );
            break;
        case cmd_query_preun_script:
            ExtractScriptContents( theHandle, rpmScriptPostuninstall, 1, stdout );
            break;
        case cmd_query_all_scripts:
            ExtractAllScripts( theHandle, stdout );
            break;
        case cmd_query_format:
            QueryFormat( theHandle, queryFormat, stdout, stderr );
            break;
        case cmd_query_tags:
        {
            int count = 0;

            for( size_t i = 1; i < sizeof( tagInfo )/sizeof( tagInfo[0] ); ++i )
            {
                RpmHeaderElementType t = GetHeaderTagType( theHandle, tagInfo[i].value, &count );

                switch( t )
                {
                    default:
                        //printf("%s (unknown type [%d]):\n", tagInfo[i].name,(int)t);
                        break;
                    case RPM_NULL_TYPE:
                        printf( "%s (null):\n", tagInfo[i].name );
                        break;
                    case RPM_CHAR_TYPE:
                    {
                        char * ptr = (char*)( GetHeaderTagAsPointer( theHandle, tagInfo[i].value, NULL, NULL ) );
                        printf( "%s (CHAR): %c\n", tagInfo[i].name, *ptr );
                        break;
                    }
                    case RPM_INT8_TYPE:
                    {
                        int8_t * ptr = (int8_t*)( GetHeaderTagAsPointer( theHandle, tagInfo[i].value, NULL, NULL ) );
                        printf( "%s (INT8): %hhd / 0x%02hhx\n", tagInfo[i].name, *ptr, *ptr );
                        break;
                    }
                    case RPM_INT16_TYPE:
                    {
                        int16_t * ptr = (int16_t*)( GetHeaderTagAsPointer( theHandle, tagInfo[i].value, NULL, NULL ) );
                        if( count == 1 )
                        {
                            int16_t swap = ntohs( *ptr );
                            printf( "%s (INT16): 0x%04hx %hd \n", tagInfo[i].name, swap, swap );
                        }
                        else
                        {
                            printf( "%s (INT16):\n", tagInfo[i].name );
                            for( int j = 0; j < count; ++j )
                            {
                                int16_t swap = ntohs( ptr[j] );
                                printf( " [%2d] -> 0x%04hx %hd \n", j, swap, swap );
                            }
                        }
                    }
                    break;
                    case RPM_INT32_TYPE:
                    {
                        int32_t * ptr = (int32_t*)( GetHeaderTagAsPointer( theHandle, tagInfo[i].value, NULL, NULL ) );
                        if( count == 1 )
                        {
                            int32_t swap = ntohl( *ptr );
                            printf( "%s (INT32): 0x%08x %d \n", tagInfo[i].name, swap, swap );
                        }
                        else
                        {
                            printf( "%s (INT32):\n", tagInfo[i].name );
                            for( int j = 0; j < count; ++j )
                            {
                                int32_t swap = ntohl( ptr[j] );
                                printf( " [%2d] -> 0x%08x %d \n", j, swap, swap );
                            }
                        }
                    }
                    break;
                    case RPM_INT64_TYPE:
                    {
                        int64_t * ptr = (int64_t*)( GetHeaderTagAsPointer( theHandle, tagInfo[i].value, NULL, NULL ) );
                        printf( "%s (INT32): 0x%lX\n", tagInfo[i].name, *ptr );
                        break;
                    }
                    case RPM_STRING_TYPE:
                    {
                        char * ptr = (char*)( GetHeaderTagAsPointer( theHandle, tagInfo[i].value, NULL, NULL ) );
                        printf( "%s (STRING): %s\n", tagInfo[i].name, ptr );
                        break;
                    }
                    case RPM_BIN_TYPE:
                    {
                        printf( "%s (BIN):\n  ", tagInfo[i].name );
                        char * ptr = (char*)( GetHeaderTagAsPointer( theHandle, tagInfo[i].value, NULL, NULL ) );

                        for( int j=0; j<count; j++ )
                        {
                            printf( "%02hhx%s", ptr[j], ( ( j+ -11 )%16 ) ? " " : "\n  " );
                        }
                        if( ( count-1 )%16 )
                        {
                            printf( "\n" );
                        }
                        break;
                    }
                    case RPM_STRING_ARRAY_TYPE:
                    {
                        printf( "%s (STRING_ARRAY):\n", tagInfo[i].name );

                        char ** array;
                        int elements;
                        GetHeaderTagAsStringArray( theHandle, tagInfo[i].value, &array, &elements );
                        for( int j = 0; j < elements; ++j )
                        {
                            printf( "  [% 2d] -> %s\n", j, array[j] );
                        }
                        FreeStringArray( array, elements );
                        break;
                    }
                    case RPM_I18NSTRING_TYPE:
                        break;
                }

            }
        }
        default:
            /* Not yet implemented. */
            break;

    }

    CloseRpmFile( theHandle );

    exit( 0 );
}

